package models

import (
	"crypto/md5"
	_ "encoding/base64"
	"fmt"
	"gitchina/kly_service/data"
	"gitchina/kly_service/errors"
	"gitchina/kly_service/util"
	"math"
	"strconv"

	"gitchina/kly_service/models/qpay"
	"github.com/pborman/uuid"
)

var TOKEN_ENCRYPT_KEY = []byte("#($&#(M<")

type Wallet struct {
	PayPwd  string  `json:"pay_pwd"`
	Balance float32 `json:"balance"`
	//Token   string  `json:"token,omitempty"`
	repo *data.PayRepo
}

type ChangeWalletPayPwd struct {
	OldPayPwd string `json:"old_pwd"`
	NewPayPwd string `json:"new_pwd"`
}

type BankCard struct {
	Id        int    `json:"card_id"`
	No        string `json:"card_no"`
	Type      int    `json:"card_type"` //1:借记卡，2:信用卡
	BankId    int    `json:"bank_id"`
	Province  string `json:"province"`
	City      string `json:"city"`
	BankName  string `json:"bank_name"`
	ValidDate string `json:"valid_date"`
	Cvv2      string `json:"cvv2"`
	SMSCode   string `json:"sms_code"`
	PayPwd    string `json:"pay_pwd,omitempty"`
}

type BindQPayCardRS struct {
	CardID int `json:"card_id"`
	State  int `json:"state"`
}

type WithdrawRQ struct {
	PayPwd string  `json:"pay_pwd"`
	CardId int     `json:"card_id"`
	Amount float32 `json:"amount"`
}

type WalletDetailListRS struct {
	TotalCount int              `json:"total_count"`
	List       []WalletDetailRS `json:"wallet_detail"`
}

type WalletDetailRS struct {
	Amount     float32 `json:"amount"`
	Balance    float32 `json:"balance"`
	TypeId     int     `json:"type_id"`
	Remark     string  `json:"remark"`
	CreateTime string  `json:"create_time"`
}

type WalletWithdrawRS struct {
	Amount           float32 `json:"amount"`
	AmountAfterRatio float32 `json:"amount_after_ratio"`
	CardNo           string  `json:"card_no"`
	State            int     `json:"state"`
	Remark           string  `json:"remark"`
	CreateTime       string  `json:"create_time"`
}

type WalletWithdrawListRS struct {
	TotalCount int                `json:"total_count"`
	List       []WalletWithdrawRS `json:"withdraw_detail"`
}

type BankCardListRS struct {
	TotalCount int        `json:"total_count"`
	List       []BankCard `json:"bankcard_list"`
}

//pattern pwd login
// func (w *Wallet) Login(memberId ID, loginToken string) (err error) {
// 	var id int64
// 	var cipherText []byte
// 	var walletDto data.WalletDto
// 	if _, _, _, id, err = memberId.Decode(); err == nil {
// 		w.repo = &data.PayRepo{}
// 		if walletDto, err = w.repo.GetWalletWithPwd(int(id), w.Pwd); err == nil {
// 			w.Balance = walletDto.Balance
// 			if cipherText, err = util.DesEncrypt([]byte(loginToken), TOKEN_ENCRYPT_KEY); err == nil {
// 				w.Token = base64.StdEncoding.EncodeToString(cipherText)
// 			}
// 		} else {
// 			util.GetLogger().Error("[wallet-Login] - error%s", err.Error())
// 			err = errors.New(errors.MSG_Wallet_Login_Error)
// 		}
// 	} else {
// 		util.GetLogger().Error("[wallet-Login] - error:unexpected mbrId:%s", string(memberId))
// 		err = errors.New(errors.MSG_Wallet_Login_Error)
// 	}
// 	return
// }

// func (w *Wallet) GetWalletByToken(memberId ID, loginToken string) (err error) {
// 	var id int64
// 	var plainText, cipherText []byte
// 	var walletDto data.WalletDto
// 	if _, _, _, id, err = memberId.Decode(); err == nil {
// 		if cipherText, err = base64.StdEncoding.DecodeString(w.Token); err == nil {
// 			if plainText, err = util.DesDecrypt(cipherText, TOKEN_ENCRYPT_KEY); err == nil {
// 				if string(plainText) == loginToken {
// 					w.repo = &data.PayRepo{}
// 					if walletDto, err = w.repo.GetWallet(int(id)); err == nil {
// 						w.Balance = walletDto.Balance
// 					}
// 				} else {
// 					err = errors.New(errors.MSG_Wallet_Invalid_Token)
// 				}
// 			} else {
// 				util.GetLogger().Error("[wallet-GetWalletByToken] - error%s", err.Error())
// 				err = errors.New(errors.MSG_Wallet_Invalid_Token)
// 			}
// 		} else {
// 			util.GetLogger().Error("[wallet-GetWalletByToken] - error%s", err.Error())
// 			err = errors.New(errors.MSG_Wallet_Invalid_Token)
// 		}
// 	} else {
// 		util.GetLogger().Error("[wallet-Login] - error:unexpected mbrId:%s", string(memberId))
// 		err = errors.New(errors.MSG_Wallet_Login_Error)
// 	}

// 	return
// }

func (w Wallet) GetBalance(memberId ID) (balance float32, err error) {
	var id int64
	var walletDto data.WalletDto
	if _, _, _, id, err = memberId.Decode(); err == nil {
		w.repo = &data.PayRepo{}
		if walletDto, err = w.repo.GetWallet(int(id)); err == nil {
			balance = walletDto.Balance
		} else {
			w.PayPwd = "123456"
			if err = w.Create(memberId); err != nil {
				util.GetLogger().Error("[wallet.create] - error:%s", err.Error())
				err = errors.New(errors.MSG_Wallet_Get_Error)
			}
		}
	} else {
		err = errors.New(errors.MSG_Wallet_Get_Error)
	}
	return
}

func (w Wallet) ChangePwd(memberId ID, oldPwd, newPwd string) (err error) {
	var id int64
	var changeOk bool
	if _, _, _, id, err = memberId.Decode(); err == nil {
		var repo data.PayRepo
		if changeOk, err = repo.UpdateWalletPwd(int(id), oldPwd, newPwd); err != nil {
			util.GetLogger().Error("[wallet-ChangePwd] - error:%s", err.Error())
			err = errors.New(errors.MSG_Wallet_Change_Pwd_Error)
		} else {
			if !changeOk {
				err = errors.New(errors.MSG_Wallet_Invalid_Pwd)
			}
		}
	} else {
		util.GetLogger().Error("[wallet-ChangePwd] - error:unexpected mbrId:%s", string(memberId))
		err = errors.New(errors.MSG_Wallet_Change_Pwd_Error)
	}
	return
}

func (w Wallet) Create(memberId ID) (err error) {
	var id int64
	if _, _, _, id, err = memberId.Decode(); err == nil {
		var repo data.PayRepo
		if alreadyCreated, err := repo.AddWallet(data.WalletDto{
			MemberId: int(id),
			Pwd:      w.PayPwd,
			Balance:  0,
		}); err != nil {
			util.GetLogger().Error("[wallet-Create] - error:%s", err.Error())
			err = errors.New(errors.MSG_Wallet_Create_Error)
		} else {
			if alreadyCreated {
				err = errors.New(errors.MSG_Wallet_Create_Error)
			}
		}
	} else {
		util.GetLogger().Error("[wallet-Create] - error:unexpected mbrId:%s", string(memberId))
		err = errors.New(errors.MSG_Wallet_Create_Error)
	}

	return
}

func (w Wallet) TopUp(memberId, typeId, waybillId int, flowId int64, amount, pBalanceAmount, pIncoming float32, needOilCard bool, oilCardNo string) (err error) {
	var remark string
	var walletDto data.WalletDto
	if w.repo == nil {
		w.repo = &data.PayRepo{}
		w.repo.BeginTx()
		defer func() {
			if err != nil {
				util.GetLogger().Error("[wallet-updateBalance] - error%s", err.Error())
			}
			w.repo.EndTx(err == nil)
		}()
	}

	if typeId == 2 {
		remark = "订金收入"
	} else if typeId == 6 {
		remark = "钱包收入(运费)"
	} else if typeId == 1 {
		remark = "充值"
	} else if typeId == 4 {
		remark = "退款"
	}

	if flowId == 0 {
		flowId, err = w.repo.NewTransFlow(data.TransFlowDto{
			MemberId: memberId,
			TransNo:  fmt.Sprintf("%x", md5.Sum([]byte(string(waybillId)))),
			Amount:   amount,
			TypeId:   typeId,
			State:    1,
			Remark:   remark,
		})
	}

	if err == nil {
		if walletDto, err = w.repo.GetWallet(memberId); err == nil {
			err = w.updateBalance(walletDto, typeId, flowId, amount, pBalanceAmount, pIncoming)
		}
	}

	return
}

func (w Wallet) updateBalance(walletDto data.WalletDto, typeId int, flowId int64, amount, pBalanceAmount, pIncoming float32) (err error) {
	//pBalanceAmount 如果是退款或定金，运费打入钱包，平台代收款需要扣除定金或运费
	//pIncoming 平台收入
	var pBalanceDto data.PlatformBalanceDto

	oldBalance := walletDto.Balance
	walletDto.Balance = walletDto.Balance + amount

	if pBalanceAmount > 0 {
		if pBalanceDto, err = w.repo.GetPlatformBalanceById(5); err == nil {
			pOldBalance := pBalanceDto.Balance
			pBalanceDto.Balance = pBalanceDto.Balance - pBalanceAmount - pIncoming
			err = w.repo.UpdatePlatformBalance(&pBalanceDto, pOldBalance)
		}
	}

	if pIncoming > 0 {
		if pBalanceDto, err = w.repo.GetPlatformBalanceById(6); err == nil {
			pOldBalance := pBalanceDto.Balance
			pBalanceDto.Balance = pBalanceDto.Balance + pIncoming
			err = w.repo.UpdatePlatformBalance(&pBalanceDto, pOldBalance)
		}
	}

	if err == nil {
		if err = w.repo.UpdateWalletBalance(&walletDto, oldBalance); err == nil {
			err = w.repo.NewWalletDetailRecord(data.WalletDetailDto{
				MemberId: walletDto.MemberId,
				FlowId:   flowId,
				Amount:   float32(math.Abs(float64(amount))),
				Balance:  walletDto.Balance,
				Balance2: walletDto.Balance2,
				TypeId:   typeId,
				Remark:   "",
			})
		}
	}

	return
}

func (w Wallet) Withdraw(memberId ID, cardID int, pwd string, amount float32) (err error) {
	var id int64
	var ratio float64
	var checkOk bool
	var walletDTO data.WalletDto
	var general General
	var minWithdrawAmount float64

	if _, _, _, id, err = memberId.Decode(); err == nil {
		if w.repo == nil {
			w.repo = &data.PayRepo{}
		}

		if minWithdrawAmount, err = strconv.ParseFloat(general.GetParams("min_withdraw_amount"), 32); err == nil {
			if amount < float32(minWithdrawAmount) {
				err = errors.New(errors.MSG_Wallet_Less_Than_Min_Withdraw_Amount)
				return
			}
		} else {
			err = errors.New(errors.MSG_Wallet_Withdraw_Error)
			return
		}

		if checkOk, err = w.repo.CheckCardBelongsToOwner(int(id), cardID); !checkOk {
			if err == nil {
				err = errors.New(errors.MSG_Wallet_Invalid_Bank_Card)
			}
		} else {
			if ratio, err = strconv.ParseFloat(general.GetParams("withdraw_ratio"), 32); err == nil {
				if walletDTO, err = w.repo.GetWalletWithPwd(int(id), pwd); err == nil {
					if walletDTO.Balance >= amount {
						balanceDeductAmount := amount * (1 + float32(ratio))
						platformIncoming := amount * float32(ratio)
						if walletDTO.Balance-balanceDeductAmount >= 0 {
							w.doWithdraw(int(id), cardID, amount, balanceDeductAmount, platformIncoming, walletDTO)
						} else {
							amount = walletDTO.Balance - platformIncoming
							w.doWithdraw(int(id), cardID, amount, walletDTO.Balance, platformIncoming, walletDTO)
						}
					} else {
						platformIncoming := walletDTO.Balance * float32(ratio)
						amount = walletDTO.Balance - platformIncoming
						w.doWithdraw(int(id), cardID, amount, walletDTO.Balance, platformIncoming, walletDTO)
					}
				}
			} else {
				err = errors.New(errors.MSG_Wallet_Withdraw_Error)
			}
		}
	} else {
		err = errors.New(errors.MSG_Wallet_Withdraw_Error)
	}

	return
}

func (w Wallet) BindBankCard(memberID ID, phone string, card BankCard) (cardID int, err error) {
	var id int64
	var mbrInfo data.MemberDto
	var mbrRepo data.MemberRepo
	w.repo = &data.PayRepo{}
	if _, _, _, id, err = memberID.Decode(); err == nil {
		if mbrInfo, err = mbrRepo.GetMemberInfo(int(id)); err == nil {
			if _, err = w.repo.GetWalletWithPwd(int(id), card.PayPwd); err == nil {
				cardID, err = w.repo.AddOrUpdateBankCard(data.BankCardDto{
					Id:       card.Id,
					MemberId: int(id),
					Name:     mbrInfo.Name,
					Phone:    phone,
					No:       card.No,
					Province: card.Province,
					City:     card.City,
					Type:     card.Type,
					BankId:   card.BankId,
					BankName: card.BankName,
				})
			} else {
				err = errors.New(errors.MSG_Wallet_Invalid_Pwd)
			}
		}
	} else {
		util.GetLogger().Error("[wallet-BindBankCard] - error:unexpected mbrId:%s", string(memberID))
		err = errors.New(errors.MSG_Wallet_Bind_Card_Error)
	}

	return
}

func (w Wallet) BindQPayBankCard(memberID ID, phone string, card BankCard) (*BindQPayCardRS, error) {
	var mrepo data.MemberRepo

	rs := new(BindQPayCardRS)
	_, _, _, mid, err := memberID.Decode()
	if err != nil {
		return nil, errors.New(errors.MSG_Wallet_Bind_Card_Error)
	}

	info, err := mrepo.GetMemberInfo(int(mid))
	if err != nil {
		return nil, errors.New(errors.MSG_Wallet_Bind_Card_Error)
	}

	w.repo = new(data.PayRepo)
	ok, b := w.repo.HasQPayCardBound(int(mid), card.No)
	if !ok {
		b, err = w.repo.AddQPayCard(data.BankCardDto{
			MemberId:    int(mid),
			Name:        info.Name,
			Phone:       phone,
			No:          card.No,
			Type:        1,
			Province:    "",
			City:        "",
			BankId:      0,
			BindId:      0,
			BankName:    "",
			IDNO:        info.IDCardNo,
			ValidDate:   card.ValidDate,
			Cvv2:        card.Cvv2,
			State:       0,
			PartnerType: 1,
			PartnerID:   "",
		})
		if err == nil {
			var result *qpay.AgreementApplyResult

			rs.CardID = b.Id

			a := qpay.NewAgreement()
			a.Cvv2 = card.Cvv2
			a.ValidDate = card.ValidDate
			a.Mobile = phone
			a.AcctNO = card.No
			a.AcctName = info.Name
			a.IDNO = info.IDCardNo
			if card.Type == 1 {
				a.AcctType = "00"
			} else {
				a.AcctType = "02"
			}
			a.MeruserID = string(memberID)
			result, err = a.Apply()
			if result.OK() {
				err = w.repo.SetQPayBankCardApplyOK(b.Id, result.Thpinfo(), result.Undecoded())
				if err == nil {
					rs.State = 1
				}
			} else {
				w.repo.UpdateQPayBankCardUndecoded(b.Id, result.Undecoded())
			}
		}
	} else {
		a := qpay.NewAgreement()
		a.Cvv2 = b.Cvv2
		a.ValidDate = b.ValidDate
		a.Mobile = phone
		a.AcctNO = b.No
		a.AcctName = info.Name
		a.IDNO = info.IDCardNo
		if card.Type == 1 {
			a.AcctType = "00"
		} else {
			a.AcctType = "02"
		}
		a.MeruserID = string(memberID)
		rs.CardID = b.Id

		if card.SMSCode != "" {
			var result *qpay.AgreementConfirmResult
			result, err = a.Confirm(card.SMSCode, b.Params)
			if result.TrxOK() {
				err = w.repo.SetQPayBankCardConfirmOK(b.Id, result.AgreeID(), result.Undecoded())
				if err == nil {
					rs.State = 2
				}
			} else {
				w.repo.UpdateQPayBankCardUndecoded(b.Id, result.Undecoded())
			}
		} else {
			var result *qpay.ResultV2
			result, err = a.Resend()
			if result.OK() {
				err = w.repo.SetQPayBankCardApplyOK(b.Id, b.Params, result.Undecoded())
				if err == nil {
					rs.State = 1
				}
			} else {
				w.repo.UpdateQPayBankCardUndecoded(b.Id, result.Undecoded())
			}
		}
	}

	if err != nil {
		util.GetLogger().Error("[BindQPayBankCard] - error:%s", err.Error())
		err = errors.New(errors.MSG_Wallet_Bind_Card_Error)
	}

	return rs, err
}

func (w Wallet) GetBankCardList(memberId ID) (rs BankCardListRS, err error) {
	var id int64
	var repo data.PayRepo
	var list []data.BankCardDto
	if _, _, _, id, err = memberId.Decode(); err == nil {
		if list, err = repo.GetBankCardList(int(id)); err == nil {
			rs.TotalCount = len(list)
			rs.List = make([]BankCard, len(list))
			for idx, val := range list {
				rs.List[idx] = BankCard{
					Id:       val.Id,
					No:       val.No,
					Type:     val.Type,
					BankId:   val.BankId,
					BankName: val.BankName,
					Province: val.Province,
					City:     val.City,
				}
			}
		} else {
			util.GetLogger().Error("[wallet-GetBankCardList] - error:%s", err.Error())
		}
	} else {
		util.GetLogger().Error("[wallet-GetBankCardList] - error:unexpected mbrId:%s", string(memberId))
	}
	return
}

func (w Wallet) GetDetailList(memberId ID, pageNo, pageSize int) (rs WalletDetailListRS, err error) {
	var id, cnt int64
	var list []data.WalletDetailDto
	if _, _, _, id, err = memberId.Decode(); err == nil {
		w.repo = &data.PayRepo{}
		if list, cnt, err = w.repo.GetWalletDetailList(int(id), pageNo, pageSize); err == nil {
			rs.TotalCount = int(cnt)
			rs.List = make([]WalletDetailRS, len(list))
			for idx, val := range list {
				rs.List[idx] = WalletDetailRS{
					Amount:     val.Amount,
					Balance:    val.Balance,
					TypeId:     val.TypeId,
					Remark:     val.Remark,
					CreateTime: val.CreateTime,
				}
			}
		}
	} else {
		util.GetLogger().Error("[wallet-GetDetail] - error:unexpected mbrId:%s", string(memberId))
		err = errors.New(errors.MSG_Wallet_Get_Detail_List_Error)
	}
	return
}

func (w Wallet) GetWithdrawList(memberId ID, pageNo, pageSize int) (rs WalletWithdrawListRS, err error) {
	var id int64
	var list []data.WithdrawDetailDto
	if _, _, _, id, err = memberId.Decode(); err == nil {
		w.repo = &data.PayRepo{}
		if list, rs.TotalCount, err = w.repo.GetWalletWithdrawList(int(id), pageNo, pageSize); err == nil {
			rs.List = make([]WalletWithdrawRS, len(list))
			for idx, val := range list {
				rs.List[idx] = WalletWithdrawRS{
					Amount:           val.Amount,
					AmountAfterRatio: val.AmountAfterRatio,
					CardNo:           val.CardNo,
					State:            val.State,
					Remark:           val.Remark,
					CreateTime:       val.CreateTime,
				}

				if val.State == 0 {
					rs.List[idx].Remark = "申请中"
				} else if val.State == 1 {
					rs.List[idx].Remark = "已受理"
				} else if val.State == 2 {
					rs.List[idx].Remark = "完成"
				} else {
					rs.List[idx].Remark = "失败"
				}
			}
		}
	} else {
		util.GetLogger().Error("[wallet-GetDetail] - error:unexpected mbrId:%s", string(memberId))
		err = errors.New(errors.MSG_Wallet_Get_Detail_List_Error)
	}
	return
}

//withdrawAmount 实际提现的钱
//balanceDeductAmount 余额中应该扣除的钱
func (w Wallet) doWithdraw(memberID, cardID int, withdrawAmount, balanceDeductAmount, platformIncoming float32, walletDTO data.WalletDto) (err error) {
	var flowID int64
	var bankCardDTO data.BankCardDto
	var platformBalanceDTO data.PlatformBalanceDto

	w.repo.BeginTx()
	defer func() {
		if err != nil {
			util.GetLogger().Error("[Wallet-TopUp] - error%s", err.Error())
		}
		w.repo.EndTx(err == nil)
	}()
	if flowID, err = w.repo.NewTransFlow(data.TransFlowDto{
		MemberId: memberID,
		TransNo:  fmt.Sprintf("%x", md5.Sum([]byte(string(uuid.New())))),
		Amount:   withdrawAmount,
		TypeId:   3,
		State:    0,
		Remark:   "提现",
	}); err == nil {
		oldBalance := walletDTO.Balance
		walletDTO.Balance = walletDTO.Balance - balanceDeductAmount
		if err = w.repo.UpdateWalletBalance(&walletDTO, oldBalance); err == nil {
			if bankCardDTO, err = w.repo.GetBankCard(cardID); err == nil {
				if err = w.repo.NewWidthDrawRecord(data.WithdrawDto{
					MemberId:         memberID,
					FlowId:           flowID,
					Amount:           withdrawAmount,
					AmountAfterRatio: balanceDeductAmount,
					CardId:           cardID,
					CardNo:           bankCardDTO.No,
					BankName:         bankCardDTO.BankName,
					CardType:         bankCardDTO.Type,
					Phone:            bankCardDTO.Phone,
					Province:         bankCardDTO.Province,
					City:             bankCardDTO.City,
					State:            0,
				}); err == nil {
					if platformBalanceDTO, err = w.repo.GetPlatformBalanceById(6); err == nil {
						pOldBalance := platformBalanceDTO.Balance
						platformBalanceDTO.Balance = platformBalanceDTO.Balance + platformIncoming
						err = w.repo.UpdatePlatformBalance(&platformBalanceDTO, pOldBalance)
					}
				}
			}
		}
	}
	return
}
